package org.budo.apache.sharding.datasource;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.shardingsphere.api.config.masterslave.LoadBalanceStrategyConfiguration;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.spi.masterslave.MasterSlaveLoadBalanceAlgorithm;
import org.budo.apache.sharding.algorithm.ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm;
import org.budo.sharding.util.ShardingUtil;
import org.budo.support.javax.sql.wrapper.DataSourceWrapper;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
@Getter
@Setter
public class ApacheShardingMasterSlaveDataSourceFactoryBean implements FactoryBean<DataSource>, ApplicationContextAware, BeanNameAware {
    private ApplicationContext applicationContext;

    private String beanName;

    private String masterDataSourceName;

    /**
     * 多从库负载算法
     */
    private MasterSlaveLoadBalanceAlgorithm loadBalanceAlgorithm = null;

    private DataSource _masterSlaveDataSource;

    /**
     * 配置 slaveDataSourceNames 则自动组装一个 默认的ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm
     */
    public void setSlaveDataSourceNames(List<String> slaveDataSourceNames) {
        this.setSlaveDataSourceNameMap(MapUtil.sameValueMap(slaveDataSourceNames, 1));
    }

    /**
     * 配置 slaveDataSourceNameMap 则自动组装一个 默认的WeightedRoundRobinMasterSlaveLoadBalanceAlgorithm
     */
    public void setSlaveDataSourceNameMap(Map<String, Integer> slaveDataSourceNameMap) {
        ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm._SLAVE_DATA_SOURCE_WEIGHT = slaveDataSourceNameMap;
        this.setLoadBalanceAlgorithm(new ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm());
    }

    @Override
    public DataSource getObject() throws Exception {
        return new DataSourceWrapper() {
            public DataSource getDataSource() { // 延迟初始化
                return ApacheShardingMasterSlaveDataSourceFactoryBean.this.masterSlaveDataSource();
            }
        };
    }

    protected DataSource masterSlaveDataSource() {
        if (null != this._masterSlaveDataSource) {
            return this._masterSlaveDataSource;
        }

        synchronized (this) {
            if (null != this._masterSlaveDataSource) {
                return this._masterSlaveDataSource;
            }

            this._masterSlaveDataSource = this.initMasterSlaveDataSource();
            log.info("#88 this._masterSlaveDataSource=" + this._masterSlaveDataSource);

            return this._masterSlaveDataSource;
        }
    }

    /**
     * 一主多从 通过slaveDataSourceNames配置多个从库的名称
     */
    private DataSource initMasterSlaveDataSource() {
        String masterDataSourceName = this.getMasterDataSourceName();
        if (StringUtil.isBlank(masterDataSourceName)) {
            throw new IllegalArgumentException("#99 masterDataSourceName is null, this=" + this);
        }

        ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm loadBalanceAlgorithm = (ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm) this.getLoadBalanceAlgorithm();
        List<String> slaveDataSourceNames = loadBalanceAlgorithm.dataSourceNames();

        Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>();

        DataSource masterDataSource = (DataSource) SpringUtil.getBean(this.getApplicationContext(), masterDataSourceName);
        dataSourceMap.put(masterDataSourceName, masterDataSource);

        for (String slaveDataSourceName : slaveDataSourceNames) {
            DataSource slaveDataSource = (DataSource) SpringUtil.getBean(this.getApplicationContext(), slaveDataSourceName);
            dataSourceMap.put(slaveDataSourceName, slaveDataSource);
        }

        // 配置读写分离规则
        String LoadBalanceAlgorithmType = ApacheShardingWeightedRoundRobinMasterSlaveLoadBalanceAlgorithm.class.getName();
        LoadBalanceStrategyConfiguration loadBalanceStrategyConfiguration = new LoadBalanceStrategyConfiguration(LoadBalanceAlgorithmType);

        MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration(this.getBeanName(), //
                masterDataSourceName, //
                slaveDataSourceNames, //
                loadBalanceStrategyConfiguration);

        return ShardingUtil.createApacheShardingMasterSlaveDataSource(dataSourceMap, masterSlaveRuleConfig);
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}